Popular Searches
Popular Course Categories
Popular Courses

Introduction to Java 8 and Modern Java Language Features

Introduction to Java 8 and Modern Java Language Features

Modern Core Java Features and Utility APIs

Introduction to Java 8 and Modern Java Language Features

Course: Core Java Training

Topic: Introduction to Java 8 and Modern Java Language Features

Course Link: Core Java Training

Demo Registration: Register for a Course Demo

Website: JustAcademy Official Website


1. Introduction to Java 8

Java 8 is a major release of the Java programming language and platform that introduced several features to make code more concise, readable, reusable, and efficient. Released in March 2014, Java 8 introduced functional programming capabilities while maintaining Java's object-oriented programming model.

Before Java 8, developers frequently used anonymous inner classes to implement interfaces, traditional loops to process collections, and older classes to handle dates and times. Java 8 introduced lambda expressions, the Stream API, functional interfaces, method references, default methods in interfaces, the Optional class, and the modern Date and Time API.

These features help developers express what a program should accomplish with less repetitive code. They are widely used in Java applications, enterprise development, data processing, and backend programming.

2. Learning Objectives

  • Understand the importance of Java 8 in Java development.
  • Identify the major language and library features introduced in Java 8.
  • Understand lambda expressions and functional interfaces.
  • Use method references to simplify code.
  • Process collections with the Stream API.
  • Understand default and static methods in interfaces.
  • Handle potentially missing values with Optional.
  • Work with dates and times using the java.time package.
  • Explore collection enhancements and modern coding practices.

3. Why Was Java 8 Introduced?

Java has traditionally emphasized object-oriented programming, in which programs are organized around classes and objects. Although this approach remains important, some tasks involving collections, data filtering, sorting, and event handling can require substantial repetitive code.

Java 8 introduced functional programming features that allow developers to represent behavior more concisely and combine operations into readable pipelines.

Limitations of Older Java Code

  • Anonymous inner classes often require lengthy implementations for simple operations.
  • Processing collections with loops can create repetitive code.
  • Interfaces could not provide ordinary default implementations before Java 8.
  • Older date and time classes had usability and thread-safety limitations.
  • Representing potentially absent values often relied on null checks.

How Java 8 Improves Development

Older Approach Java 8 Improvement
Anonymous inner classes for simple behavior Lambda expressions
Long collection-processing loops Stream API operations
Repeated method-call lambda bodies Method references
Interfaces containing only abstract methods and constants Default and static methods in interfaces
Null values handled through repeated checks Optional for representing potentially absent values
Older date and time classes Modern java.time API

4. Major Features Introduced in Java 8

Java 8 includes language improvements, new APIs, and enhancements to existing libraries.

Feature Description Main Benefit
Lambda Expressions Represent behavior using compact expressions. Less repetitive code.
Functional Interfaces Interfaces with one abstract method. Provide target types for lambdas.
Method References Refer directly to existing methods. Improve readability.
Stream API Process sequences of data through operations. Declarative collection processing.
Default Methods Provide implementations inside interfaces. Extend interfaces compatibly.
Static Interface Methods Define utility methods inside interfaces. Keep related behavior together.
Optional Represent a value that may be absent. Make absence explicit.
Date and Time API Modern classes for dates, times, and time zones. Clearer date and time handling.
Collection Enhancements Methods such as forEach, removeIf, and replaceAll. Simplify common operations.
Repeating Annotations Apply the same annotation type multiple times. More flexible metadata.
Type Annotations Apply annotations to type usages. Support stronger type checking tools.
CompletableFuture Compose asynchronous computations. More flexible asynchronous programming.

5. Understanding Lambda Expressions

A lambda expression is a concise way to represent an implementation of a functional interface. It allows behavior to be passed as an argument without creating a separate named class or writing a full anonymous inner class.

Lambda expressions are one of the most important features introduced in Java 8.

Basic Syntax

(parameters) -> expression

A lambda expression contains a parameter list, the arrow operator ->, and either an expression or a block of statements.

Example 1: A Simple Lambda Expression

public class LambdaExample {
    public static void main(String[] args) {
        Runnable task = () -> System.out.println("Hello Java 8!");

        task.run();
    }
}

Output:

Hello Java 8!

Explanation: The lambda expression provides the implementation of the run() method declared by the Runnable interface. Calling task.run() executes the supplied behavior on the current thread.

Example 2: Lambda with Parameters

public class LambdaAddition {
    public static void main(String[] args) {
        Calculator calculator = (a, b) -> a + b;

        System.out.println(calculator.add(10, 20));
    }
}

interface Calculator {
    int add(int a, int b);
}

Output:

30

Explanation: The lambda expression implements the single abstract method of the Calculator interface and returns the sum of two integers.

Advantages of Lambda Expressions

  • Reduce boilerplate code.
  • Make short implementations easier to read.
  • Support functional-style programming.
  • Work naturally with collection methods and streams.
  • Allow behavior to be passed to reusable methods.

6. Functional Interfaces

A functional interface is an interface that has exactly one abstract method. It can contain default methods and static methods in addition to that abstract method.

Functional interfaces provide the target types required by lambda expressions and method references.

Example

@FunctionalInterface
interface Greeting {
    void sayHello();
}

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        Greeting greeting =
            () -> System.out.println("Welcome to Java 8!");

        greeting.sayHello();
    }
}

Output:

Welcome to Java 8!

The @FunctionalInterface annotation is optional, but it helps the compiler detect accidental changes that would introduce another abstract method.

Common Built-in Functional Interfaces

Interface Purpose Example
Predicate<T> Tests a condition and returns a boolean. Check whether a number is positive.
Function<T,R> Transforms a value of type T into type R. Convert a name to uppercase.
Consumer<T> Accepts a value and performs an action without returning a result. Print a name.
Supplier<T> Provides a value without accepting an input argument. Generate a message.
UnaryOperator<T> Accepts and returns the same type. Increase an integer.
BinaryOperator<T> Combines two values of the same type into one value of that type. Add two integers.

Example Using Predicate

import java.util.function.Predicate;

public class PredicateExample {
    public static void main(String[] args) {
        Predicate isEven = number -> number % 2 == 0;

        System.out.println(isEven.test(10));
        System.out.println(isEven.test(7));
    }
}

Output:

true
false

7. Method References

A method reference is a compact way to refer to an existing method when a lambda expression would simply call that method. The double-colon operator :: is used to create method references.

Example

import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Amit", "Neha", "Rahul");

        names.forEach(System.out::println);
    }
}

Output:

Amit
Neha
Rahul

Explanation: The expression System.out::println refers to the existing println method instead of writing a lambda that calls it for every name.

Types of Method References

Type Example Meaning
Static method reference Integer::parseInt References a static method.
Instance method reference on a particular object System.out::println References a method on a specific object.
Instance method reference on an arbitrary object of a type String::toUpperCase References an instance method for an object supplied as an argument.
Constructor reference ArrayList::new References a constructor.

8. Introduction to the Stream API

The Stream API allows developers to process sequences of elements using operations such as filtering, mapping, sorting, and reducing. It was introduced in Java 8 and works with collections and other data sources.

A stream does not normally store data itself. Instead, it processes elements from a source, such as a list or set, through a sequence of operations.

Example: Filtering Even Numbers

import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers =
            Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);

        numbers.stream()
               .filter(number -> number % 2 == 0)
               .forEach(System.out::println);
    }
}

Output:

2
4
6
8

Important Stream Operations

Operation Purpose Example
filter() Selects elements matching a condition. filter(n -> n > 5)
map() Transforms each element. map(String::toUpperCase)
sorted() Sorts elements. sorted()
distinct() Removes duplicate elements. distinct()
limit() Restricts the number of elements. limit(3)
count() Counts elements. count()
reduce() Combines elements into a result. reduce(0, Integer::sum)
collect() Accumulates stream elements into a result container. collect(Collectors.toList())

Example: Filtering and Transforming Data

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamProcessingExample {
    public static void main(String[] args) {
        List<String> names =
            Arrays.asList("amit", "neha", "arjun", "riya");

        List<String> result = names.stream()
            .filter(name -> name.startsWith("a"))
            .map(String::toUpperCase)
            .collect(Collectors.toList());

        System.out.println(result);
    }
}

Output:

[AMIT, ARJUN]

Explanation: The stream first filters names that start with the letter a, converts the matching names to uppercase, and collects the results into a list.

9. Intermediate and Terminal Stream Operations

Stream operations are commonly divided into intermediate and terminal operations.

Intermediate Operations

Intermediate operations return another stream and can be chained together. They are generally lazy, meaning their processing begins when a terminal operation is invoked.

Examples include:

  • filter()
  • map()
  • sorted()
  • distinct()
  • limit()

Terminal Operations

Terminal operations produce a result or a side effect and trigger the processing of the stream pipeline.

Examples include:

  • forEach()
  • collect()
  • count()
  • reduce()
  • anyMatch()

A stream should generally be consumed only once. If another processing pipeline is required, create a new stream from the source.

10. Default Methods in Interfaces

Before Java 8, interfaces could declare abstract methods but could not provide ordinary default implementations. Java 8 introduced default methods, which allow interfaces to include method implementations using the default keyword.

This feature helps library designers add functionality to existing interfaces while maintaining compatibility with existing implementations in many cases.

Example

interface Vehicle {
    void start();

    default void stop() {
        System.out.println("Vehicle stopped.");
    }
}

public class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car started.");
    }

    public static void main(String[] args) {
        Car car = new Car();
        car.start();
        car.stop();
    }
}

Output:

Car started.
Vehicle stopped.

Explanation: The Car class implements the abstract start() method but inherits the default implementation of stop().

11. Static Methods in Interfaces

Java 8 also allows interfaces to define static methods. These methods are associated with the interface itself and are called using the interface name.

Example

interface CalculatorUtility {
    static int add(int a, int b) {
        return a + b;
    }
}

public class StaticInterfaceMethodExample {
    public static void main(String[] args) {
        int result = CalculatorUtility.add(10, 20);
        System.out.println(result);
    }
}

Output:

30

Explanation: The static method is called directly using CalculatorUtility.add(). It is not inherited as an instance method by implementing classes.

12. Understanding Optional

The Optional class, introduced in Java 8, represents a value that may or may not be present. It can make the possibility of a missing result explicit and reduce some accidental null-handling errors.

Optional is particularly useful for method return values when an expected result might be absent. It does not eliminate all null-related problems, and it should not be used automatically for every field or method parameter.

Example

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> name = Optional.of("Rahul");

        System.out.println(name.orElse("Guest"));

        Optional<String> emptyName = Optional.empty();
        System.out.println(emptyName.orElse("Guest"));
    }
}

Output:

Rahul
Guest

Common Optional Methods

Method Purpose
Optional.of(value) Creates an Optional containing a non-null value.
Optional.ofNullable(value) Creates an Optional that may be empty if the supplied value is null.
Optional.empty() Creates an empty Optional.
isPresent() Checks whether a value is present.
ifPresent(action) Runs an action when a value is present.
orElse(defaultValue) Returns the value or a fallback value.
orElseGet(supplier) Obtains a fallback value from a supplier when empty.
map(function) Transforms a present value.

13. Java 8 Date and Time API

Java 8 introduced the java.time package, which provides a more modern approach to handling dates, times, durations, and time zones.

Many classes in this API are immutable and thread-safe, making date and time operations easier to understand and maintain.

Important Classes

Class Purpose Example
LocalDate Represents a date without a time zone. 2026-10-26
LocalTime Represents a time without a date or time zone. 10:30:00
LocalDateTime Represents a date and time without a time zone. 2026-10-26T10:30:00
ZonedDateTime Represents a date and time with a time zone. Date and time in Asia/Kolkata.
Period Represents a date-based amount of time. Two months.
Duration Represents a time-based amount of time. Two hours.
DateTimeFormatter Formats and parses date and time values. 26-10-2026

Example: Working with Dates and Times

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateTimeExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.now();
        LocalTime time = LocalTime.now();
        LocalDateTime dateTime = LocalDateTime.now();

        DateTimeFormatter formatter =
            DateTimeFormatter.ofPattern("dd-MM-yyyy");

        System.out.println(date.format(formatter));
        System.out.println(time);
        System.out.println(dateTime);
    }
}

Explanation: The program obtains the current date, time, and date-time. The formatted date uses the pattern dd-MM-yyyy. Actual output depends on the machine's clock and environment.

14. Enhancements to the Collections Framework

Java 8 added several methods to existing collection interfaces and classes. These methods make it easier to perform operations on lists, sets, and maps.

Important Collection Methods

Method Purpose
forEach() Performs an action for each element.
removeIf() Removes elements matching a predicate.
replaceAll() Replaces list elements using a supplied operator.
sort() Sorts a list using a comparator.
getOrDefault() Returns a map value or a default when the key is absent.
computeIfAbsent() Computes and stores a value when a key has no mapping.
merge() Combines a supplied value with an existing map value.

Example: Removing Elements from a List

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CollectionEnhancementExample {
    public static void main(String[] args) {
        List<Integer> numbers =
            new ArrayList<>(Arrays.asList(10, 15, 20, 25, 30));

        numbers.removeIf(number -> number < 20);

        System.out.println(numbers);
    }
}

Output:

[20, 25, 30]

15. Parallel Streams

Java 8 introduced support for parallel stream processing through the Stream API. A parallel stream can divide certain operations across multiple threads, potentially improving performance for suitable workloads.

Example

import java.util.Arrays;
import java.util.List;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers =
            Arrays.asList(1, 2, 3, 4, 5, 6);

        numbers.parallelStream()
               .forEach(System.out::println);
    }
}

Important: The output order is not guaranteed. Parallel streams are not automatically faster than sequential streams. Performance depends on data size, operation cost, workload characteristics, and thread coordination overhead.

When to Consider Parallel Streams

  • Processing sufficiently large collections.
  • Performing computationally intensive operations that can be divided safely.
  • Using operations that do not rely on shared mutable state.
  • Benchmarking shows a measurable improvement over sequential processing.

16. CompletableFuture and Asynchronous Programming

CompletableFuture was introduced in Java 8 to support asynchronous computations and the composition of dependent tasks. It allows developers to run work asynchronously, transform results, combine tasks, and handle errors.

Example

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture<String> future =
            CompletableFuture.supplyAsync(() -> "Hello");

        CompletableFuture<String> result =
            future.thenApply(message -> message + " Java 8");

        System.out.println(result.join());
    }
}

Output:

Hello Java 8

Explanation: supplyAsync() starts an asynchronous computation, and thenApply() transforms its result. The join() method waits for the result and returns it when available.

17. Repeating Annotations and Type Annotations

Repeating Annotations

Java 8 allows the same annotation type to be applied multiple times to a declaration when that annotation is designed to support repetition using @Repeatable.

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Roles.class)
@interface Role {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@interface Roles {
    Role[] value();
}

@Role("Admin")
@Role("Editor")
class UserAccount {
}

This example defines a repeatable annotation that can assign multiple role labels to the same class. The annotations themselves do not implement access control; application code must interpret them appropriately.

Type Annotations

Java 8 allows annotations to be applied in type-use positions, such as generic type arguments and type declarations. These annotations can support tools that perform additional checks on code.

import java.util.List;

class TypeAnnotationExample {
    private List<@Deprecated String> names;
}

This is a simplified illustration of annotation placement. In production code, a custom annotation designed for type use is generally more useful than applying @Deprecated to a type argument.

18. Traditional Java vs Java 8: Practical Comparison

Traditional Anonymous Inner Class

Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Task executed");
    }
};

task.run();

Java 8 Lambda Expression

Runnable task = () -> System.out.println("Task executed");

task.run();

The lambda version expresses the same behavior with less code. This is especially useful when implementing simple functional interfaces.

Traditional Collection Processing

List<Integer> numbers =
    Arrays.asList(1, 2, 3, 4, 5, 6);

for (Integer number : numbers) {
    if (number % 2 == 0) {
        System.out.println(number);
    }
}

Java 8 Stream Processing

List<Integer> numbers =
    Arrays.asList(1, 2, 3, 4, 5, 6);

numbers.stream()
       .filter(number -> number % 2 == 0)
       .forEach(System.out::println);

Both approaches print even numbers. The Stream API separates the filtering rule from the action performed on each matching element.

19. Best Practices for Using Java 8 Features

  • Use lambda expressions when they make behavior clearer than anonymous inner classes.
  • Keep lambda bodies short and easy to understand.
  • Use method references when they express the intended behavior more clearly.
  • Build stream pipelines from small, meaningful operations.
  • Avoid modifying shared mutable state inside stream operations.
  • Do not assume parallel streams will always improve performance.
  • Use Optional mainly to represent potentially absent return values.
  • Prefer immutable date and time objects from java.time for modern date handling.
  • Use default methods thoughtfully when evolving interfaces.
  • Test exception handling and boundary conditions.
  • Check the required Java version before using language or library features.

20. Practical Mini Project: Student Marks Analyzer

Objective: Create a small Java application that processes student marks using Java 8 lambda expressions, streams, and method references.

Requirements

  • Store student marks in a list.
  • Display all marks.
  • Filter marks greater than or equal to 60.
  • Convert matching marks into a separate list.
  • Calculate the number of passing marks.
  • Calculate the average mark.

Sample Solution

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StudentMarksAnalyzer {
    public static void main(String[] args) {
        List<Integer> marks =
            Arrays.asList(45, 78, 90, 55, 66, 82);

        System.out.println("All marks:");
        marks.forEach(System.out::println);

        List<Integer> passingMarks = marks.stream()
            .filter(mark -> mark >= 60)
            .collect(Collectors.toList());

        long passCount = passingMarks.size();

        double average = marks.stream()
            .mapToInt(Integer::intValue)
            .average()
            .orElse(0.0);

        System.out.println("Passing marks: " + passingMarks);
        System.out.println("Number of passes: " + passCount);
        System.out.println("Average marks: " + average);
    }
}

Expected Output

All marks:
45
78
90
55
66
82
Passing marks: [78, 90, 66, 82]
Number of passes: 4
Average marks: 69.33333333333333

Concepts Practiced

  • List collections.
  • Lambda expressions.
  • Method references.
  • Stream filtering.
  • Collection of stream results.
  • Primitive streams and average calculation.
  • OptionalDouble fallback handling through orElse().

21. Interview Questions and Answers

Q1. What is Java 8?

Java 8 is a major Java release that introduced features such as lambda expressions, the Stream API, functional interfaces, default methods, Optional, and the modern Date and Time API.

Q2. What is the main purpose of lambda expressions?

Lambda expressions provide a concise way to implement functional interfaces and pass behavior as an argument.

Q3. What is a functional interface?

A functional interface has exactly one abstract method. It may also contain default and static methods.

Q4. What is the difference between a lambda expression and a method reference?

A lambda expression defines behavior directly, while a method reference refers to an existing method that matches the required functional interface.

Q5. What is the Stream API?

The Stream API supports declarative processing of sequences of elements using operations such as filtering, mapping, sorting, and reduction.

Q6. What is the difference between intermediate and terminal operations?

Intermediate operations return another stream and are generally lazy. Terminal operations produce a result or side effect and trigger pipeline processing.

Q7. What is a default method in an interface?

A default method is an interface method with an implementation declared using the default keyword.

Q8. What is Optional in Java 8?

Optional represents a value that may or may not be present and provides methods for working with absent values more explicitly.

Q9. Why was the java.time package introduced?

It provides a modern API for representing and manipulating dates, times, durations, periods, and time zones.

Q10. What is a method reference?

A method reference uses the :: operator to refer to an existing method or constructor when a compatible functional interface is expected.

Q11. What is the difference between stream() and parallelStream()?

stream() creates a sequential stream, while parallelStream() creates a stream that can process operations in parallel. Parallel execution is not guaranteed to be faster.

Q12. What is CompletableFuture?

CompletableFuture supports asynchronous computations and allows developers to compose dependent tasks and handle their results.

Q13. Can an interface contain static methods in Java 8?

Yes. Java 8 allows interfaces to declare static methods, which are called using the interface name.

Q14. What is the purpose of Predicate?

Predicate<T> represents a condition that accepts an input and returns a boolean result. It is commonly used with stream filtering.

Q15. What is the advantage of Java 8 for collection processing?

Lambda expressions, streams, and enhanced collection methods reduce repetitive code and make data transformations easier to express.

22. Quick Revision Checklist

  • Understand Java 8 and its major improvements.
  • Learn lambda expression syntax and functional interfaces.
  • Practice Predicate, Function, Consumer, and Supplier.
  • Use method references with the :: operator.
  • Understand stream pipelines and common operations.
  • Differentiate intermediate and terminal operations.
  • Use default and static methods in interfaces.
  • Represent potentially absent values with Optional where appropriate.
  • Use LocalDate, LocalTime, and LocalDateTime.
  • Practice collection enhancements and CompletableFuture.
  • Understand the trade-offs of parallel streams.
  • Verify Java version compatibility before compiling examples.

23. Conclusion

Java 8 introduced important language features and APIs that changed how developers write modern Java applications. Lambda expressions and functional interfaces make behavior easier to represent, while method references and the Stream API simplify collection processing.

Default methods help evolve interfaces, Optional supports clearer handling of absent values, and the java.time package provides a modern approach to date and time operations. CompletableFuture also enables developers to compose asynchronous tasks.

Mastering these features builds a strong foundation for modern Core Java programming and prepares developers to work with contemporary Java frameworks and backend applications.

24. Additional Learning Resources

To continue learning, explore the Core Java Training course at JustAcademy, visit the JustAcademy website, or register for a course demo.

Previous Next
whatsapp